﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Runtime.Internal.Util;

namespace ConsoleApp2
{
    public class CompressProcess
    {

        //private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        private BlockingCollection<string> _fileQueue = new BlockingCollection<string>(new ConcurrentQueue<string>());
        private CancellationTokenSource _cts;
        private Task _compressionTask;
        private bool _isCompressing = false;

        private long queueBytes = 0;
        private int queueFiles = 0;

        private FileStream zipStream;
        private ZipArchive archive;


        // 动态添加单个文件到队列
        public void AddFile(string filePath)
        {
            if (File.Exists(filePath))
            {
                _fileQueue.Add(filePath);


                queueBytes += new FileInfo(filePath).Length;
                queueFiles += 1;
            }
            else
            {
                Console.WriteLine($"文件不存在: {filePath}");
            }
        }

        // 动态添加多个文件到队列
        public void AddFiles(IEnumerable<string> filePaths)
        {
            foreach (var path in filePaths)
            {
                AddFile(path);
            }
        }

        public void CompleteAdding()
        {
            _fileQueue.CompleteAdding(); // 标记队列结束
        }


        public async Task WaitForCompletionAsync()
        {

            await _compressionTask;
            archive?.Dispose();
            zipStream?.Dispose();
        }

        // 停止压缩任务
        public async Task StopCompressionAsync(bool deleteIncompleteFile = true)
        {
            if (!_isCompressing) return;

            _cts.Cancel();
            await _compressionTask;

            // 删除未完成的 ZIP 文件（可选）
            if (deleteIncompleteFile && (_compressionTask.IsFaulted || _compressionTask.IsCanceled))
            {
                //await RetryDeleteFileAsync(_compressionTask.AsyncState as string, maxRetries: 3);
            }
        }


        public async Task StartCompressFilesAsync(
            long totalBytes,
            int totalFiles,
            string destinationZipPath,
            CancellationTokenSource cts,
            IProgress<CompressionProgress> progress = null,
            int bufferSize = 50 * 1024 * 1024)
        {

            _isCompressing = true;
            //_cts = new CancellationTokenSource();
            _cts = cts;

            var processedBytes = new ConcurrentLong();

            zipStream = new FileStream(
                destinationZipPath,
                FileMode.Create,
                FileAccess.Write,
                FileShare.None,
                bufferSize,
                FileOptions.Asynchronous);

            archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
            var lockObj = new object();

            //NLogHelper.Log(logger, LogLevel.Error, $"CompressProcess 开始");

            try
            {

                int processedFiles = 0;
                _compressionTask = Task.Run(() =>
                {
                    Parallel.ForEach(_fileQueue.GetConsumingEnumerable(), new ParallelOptions
                    {
                        CancellationToken = _cts.Token,
                        MaxDegreeOfParallelism = Environment.ProcessorCount
                    }, filePath =>
                    {
                        lock (lockObj)
                        {
                            _cts.Token.ThrowIfCancellationRequested();

                            var fileInfo = new FileInfo(filePath);
                            using var fileStream = new FileStream(
                                fileInfo.FullName,
                                FileMode.Open,
                                FileAccess.Read,
                                FileShare.Read,
                                bufferSize,
                                FileOptions.SequentialScan);

                            ZipArchiveEntry entry;

                            entry = archive.CreateEntry(
                                fileInfo.Name,
                                CompressionLevel.Optimal);



                            using var entryStream = entry.Open();
                            var buffer = new byte[bufferSize];
                            long fileBytesProcessed = 0;

                            //NLogHelper.Log(logger, LogLevel.Error, $"CompressProcess 处理文件:{filePath}");

                            while (true)
                            {
                                _cts.Token.ThrowIfCancellationRequested();

                                var bytesRead = fileStream.Read(buffer, 0, buffer.Length);
                                if (bytesRead == 0) break;

                                entryStream.Write(buffer, 0, bytesRead);
                                fileBytesProcessed += bytesRead;

                                // 更新进度
                                var total = processedBytes.Add(bytesRead);
                                //NLogHelper.Log(logger, LogLevel.Error, $"CompressProcess 报告进度:{filePath}");
                                progress?.Report(new CompressionProgress
                                {
                                    ProcessedFiles = processedFiles,
                                    TotalBytes = totalBytes,
                                    ProcessedBytes = total,
                                    CurrentFile = fileInfo.Name,
                                    CurrentFileProgress = (double)fileBytesProcessed / fileInfo.Length,
                                    TotalFiles = totalFiles,
                                    QueueBytes = queueBytes,
                                    QueueFiles = queueFiles
                                });
                            }

                            processedFiles++;

                        }
                    });
                }, _cts.Token);
            }
            catch (OperationCanceledException e)
            {
                //NLogHelper.Log(logger, LogLevel.Error, $"取消压缩:{e.ToString()}");
                if (File.Exists(destinationZipPath))
                {
                    archive.Dispose();
                    zipStream.Dispose();  // 强制释放资源

                    // 增加文件句柄释放等待机制
                    int retryCount = 0;
                    const int maxRetries = 3;
                    bool deleteSuccess = false;

                    while (retryCount < maxRetries && !deleteSuccess)
                    {
                        try
                        {
                            File.Delete(destinationZipPath);
                            deleteSuccess = true;
                        }
                        catch (IOException ex) when (ex.HResult == 0x80070020)
                        {
                            // 文件仍被占用时重试 :ml-citation{ref="5,8" data="citationList"}
                            retryCount++;
                            await Task.Delay(100 * retryCount);
                        }
                        catch (IOException ex)
                        {
                            Console.WriteLine($"清理失败: {ex.Message}");
                            break;
                        }
                    }
                }
                throw;
            }



        }

        private static long CalculateTotalSize(IEnumerable<string> files)
        {
            return files.Sum(f => new FileInfo(f).Length);
        }

        private class ConcurrentLong
        {
            private long _value;
            public long Add(long value) => Interlocked.Add(ref _value, value);
            public long Value => Interlocked.Read(ref _value);
        }
    }

    public class CompressionProgress
    {
        public int ProcessedFiles { get; set; }
        public int TotalFiles { get; set; }
        public long TotalBytes { get; set; }
        public int QueueFiles { get; set; }
        public long QueueBytes { get; set; }
        public long ProcessedBytes { get; set; }
        public string CurrentFile { get; set; }
        public double CurrentFileProgress { get; set; }

        public double OverallProgress =>
            TotalBytes == 0 ? 0 : (double)ProcessedBytes / TotalBytes;
    }

}
